package com.example.demoddd.config;


import com.example.demoddd.application.services.UserServiceApplicationService;
import com.example.demoddd.domain.userauth.filter.JwtAuthenticationFilter;
import com.example.demoddd.domain.userauth.handler.AuthLimitHandler;
import com.example.demoddd.domain.userauth.handler.AuthenticationEntryPointHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //配合@PreAuthorize使用
@RequiredArgsConstructor
public class SecurityConfig {
    private final ApplicationEventPublisher applicationEventPublisher;
    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final UserServiceApplicationService userServiceApplicationService;


    @Bean
    public PasswordEncoder passwordEncoder() {
        //return  new BCryptPasswordEncoder();
        return NoOpPasswordEncoder.getInstance();
    }

    //将AuthenticationManager配置成Bean

    /**
     * 这个Bean创建了一个认证管理器对象，它是Spring Security认证的核心组件之一。
     * 认证管理器负责协调和管理认证流程，并委托给一个或多个认证提供者（在这里，使用了daoAuthenticationProvider）来进行具体的认证操作。
     * 这里通过创建一个ProviderManager对象，将之前配置的daoAuthenticationProvider添加到认证管理器中。
     * 还通过setAuthenticationEventPublisher()方法设置了一个事件发布器，用于在认证事件发生时发布相关的事件，
     * 这里使用了DefaultAuthenticationEventPublisher，并传入了一个applicationEventPublisher对象，可能用于发布认证事件到Spring的事件机制中。
     *
     * @return
     */

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        //return configuration.getAuthenticationManager();
        List<AuthenticationProvider> providers = new ArrayList<>();
        providers.add(authenticationProvider());
        ProviderManager providerManager = new ProviderManager(providers);
        providerManager.setAuthenticationEventPublisher(new DefaultAuthenticationEventPublisher(applicationEventPublisher));

        return providerManager;
    }

    //将AuthenticationProvider注册成Bean

    /**
     * 组装认证的
     * AuthenticationProvider 是Spring Security用于处理基于数据库的用户认证的提供者。
     * DaoAuthenticationProvider需要一个UserDetailsService对象来获取用户的详细信息进行认证，
     * 所以通过setUserDetailsService()方法设置了我们之前设置的manager。
     *
     * @return
     */
    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setUserDetailsService(userServiceApplicationService);
        return daoAuthenticationProvider;
    }


    /**
     * 没有登录或认证信息无效时  的处理器
     *
     * @return
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPointHandler() {
        AuthenticationEntryPointHandler authenticationEntryPointHandler = new AuthenticationEntryPointHandler();
        return authenticationEntryPointHandler;
    }

    /**
     * 权限验证异常处理器
     *
     * @return
     */
    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        AuthLimitHandler authLimitHandler = new AuthLimitHandler();
        return authLimitHandler;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {

        //前后分离 取消默认的登录页
        httpSecurity.formLogin(AbstractHttpConfigurer::disable);
        //取消默认 登出页
        httpSecurity.logout(AbstractHttpConfigurer::disable);
        //前后分离 禁用csrf
        httpSecurity.csrf(AbstractHttpConfigurer::disable);
        //使用jwt 关闭session
        httpSecurity.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        //请求使用post json  httpBasic用不上
        httpSecurity.httpBasic(AbstractHttpConfigurer::disable);

        //CSP通过制定有效的源列表来控制页面可以加载的资源类型，例如脚本、图片、字体、样式表、媒体等
        //要允许从相同源加载所有资源类型，可以设置如下： default-src 'self'
        httpSecurity.headers(headers -> headers.contentSecurityPolicy(Customizer.withDefaults()));

        // 将api-docs和swagger-ui相关资源设置为允许匿名访问
        httpSecurity.authorizeHttpRequests((req) -> {
            //需要放过的
            //放过api-docs
            req.requestMatchers("/v3/api-docs").permitAll();
            req.requestMatchers("/v3/api-docs/**").permitAll();
            //放过 swagger
            req.requestMatchers("/swagger-ui.html").permitAll();
            req.requestMatchers("/swagger-ui/**").permitAll();
            //其他需要放开的接口
            req.requestMatchers(HttpMethod.POST,
                    "/login"
            ).permitAll();

            // 其余资源登录后方可访问
            req.anyRequest().authenticated();
        });

        //AuthenticationProvider  需要的 用户操作
        //httpSecurity.authenticationProvider()


        //将用户授权时用到的JWT校验过滤器添加进SecurityFilterChain中，并放在UsernamePasswordAuthenticationFilter的前面
        httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);


        //.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
        //httpSecurity.oauth2ResourceServer((oauth2Resource) -> oauth2Resource.jwt(Customizer.withDefaults()));
        //请求异常处理器
        httpSecurity.exceptionHandling(
                (exceptions) -> exceptions
                        //authenticationEntryPoint 没有登录或认证信息无效时
                        //.authenticationEntryPoint(new AuthenticationEntryPointHandler())
                        //.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
                        .authenticationEntryPoint(authenticationEntryPointHandler())

                        //accessDeniedHandler 权限异常处理器
                        .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
                        .accessDeniedHandler(accessDeniedHandler())

        );

        return httpSecurity.build();
    }

}

